#include "renderer.h"

#include <fstream>
#include <iostream>
#include <vector>
#include <stdio.h>
#include <string>
#define GLFW_DLL
#include <gl/glfw.h>

#include "file.h"
#include "gamestate.h"
#include "module.h"
#include "model.h"

Renderer* renderer;

float* vertexData;

Renderer::Renderer(int width, int height):width(width),height(height){
    fFrustumScale = 1.0f;
	fzNear = 0.1f;
	fzFar = 3.0f;
}

void Renderer::WindowResizeCallback(int w, int h){
    renderer->Reshape(w, h);
}

void Renderer::Reshape(int w, int h){
    perspectiveMatrix[0] = fFrustumScale / (w / (float)h);
    perspectiveMatrix[5] = fFrustumScale;
    glUseProgram(shaderProgram);
    glUniformMatrix4fv(perspectiveMatrixUnf, 1, GL_FALSE, perspectiveMatrix);
    glUseProgram(0);
    glViewport(0, 0, (GLsizei)w, (GLsizei)h);
}

void Renderer::InitialiseProgram(){
    const char* vertShaderText = File::GetTextFileContents("shaders/module.vert");
    const char* fragShaderText = File::GetTextFileContents("shaders/module.frag");
    GLint vertShadErr;
    GLint fragShadErr;
    GLint progLinkErr;

    vertShaderRef = glCreateShader(GL_VERTEX_SHADER);
    if(fragShaderRef){
        std::cout<<"Vertex shader created\n";
    }
    fragShaderRef = glCreateShader(GL_FRAGMENT_SHADER);
    if(fragShaderRef){
        std::cout<<"Fragment shader created\n";
    }

    glShaderSource(vertShaderRef, 1, &vertShaderText, NULL);
    glShaderSource(fragShaderRef, 1, &fragShaderText, NULL);

    glCompileShader(vertShaderRef);
    glGetShaderiv(vertShaderRef, GL_COMPILE_STATUS, &vertShadErr);
    if(vertShadErr==GL_TRUE){
        std::cout<<"Vertex shader compiled\n";
    }
    else{
        std::cout<<"Vertex shader compiling failed\n";
    }

    glCompileShader(fragShaderRef);
    glGetShaderiv(fragShaderRef, GL_COMPILE_STATUS, &fragShadErr);
    if(fragShadErr==GL_TRUE){
        std::cout<<"Fragment shader compiled\n";
    }
    else{
        std::cout<<"Fragment shader compiling failed\n";
    }

    shaderProgram = glCreateProgram();

    glAttachShader(shaderProgram, vertShaderRef);
    glAttachShader(shaderProgram, fragShaderRef);

    glLinkProgram(shaderProgram);
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &progLinkErr);
    if(progLinkErr==GL_TRUE){
        std::cout<<"Shader program linked\n";
    }
    else{
        std::cout<<"Shader program linking failed\n";
    }

// Perspective matrix
	perspectiveMatrixUnf = glGetUniformLocation(shaderProgram, "perspectiveMatrix");
	std::cout<<"Perspective uniform: "<<perspectiveMatrixUnf<<std::endl;

	for(auto& i:perspectiveMatrix){
	    i = 0;
	}
    perspectiveMatrix[0] = fFrustumScale;
	perspectiveMatrix[5] = fFrustumScale;
	perspectiveMatrix[10] = (fzFar + fzNear) / (fzNear - fzFar);
	perspectiveMatrix[14] = (2 * fzFar * fzNear) / (fzNear - fzFar);
	perspectiveMatrix[11] = -1.0f;

    glUseProgram(shaderProgram);
    glUniformMatrix4fv(perspectiveMatrixUnf, 1, GL_FALSE, perspectiveMatrix);
    glUseProgram(0);
// Offset
    offsetUnf = glGetUniformLocation(shaderProgram, "offset");
    std::cout<<offsetUnf<<std::endl;
}

void Renderer::InitialiseVertexBuffer(){
//    Model *model = new Model("data/module.obj");
//    Model *model = new Model("data/smallcube.obj");
//    Model *model = new Model("data/cube.obj");
    Model *model = new Model("data/longblock.obj");
    vertexData = model->GetVertexData();
    int size = model->VertexCount()*4*sizeof(float);

    glGenBuffers(1, &vertexDataVBO);

    glBindBuffer(GL_ARRAY_BUFFER, vertexDataVBO);
    glBufferData(GL_ARRAY_BUFFER, size, vertexData, GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
}

void Renderer::InitialiseVertexArrayObjects(){
    glGenVertexArrays(1, &vao1);
    glBindVertexArray(vao1);

    glBindBuffer(GL_ARRAY_BUFFER, vertexDataVBO);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);

    glBindVertexArray(0);
}

void Renderer::Initialise(){
	InitialiseProgram();
	InitialiseVertexBuffer();
	InitialiseVertexArrayObjects();

    Reshape(width, height);

    glEnable(GL_DEPTH_TEST);
    glDepthMask(GL_TRUE);
    glDepthFunc(GL_LEQUAL);
    glDepthRange(0.0f, 1.0f);

//	glEnable(GL_CULL_FACE);
//    glCullFace(GL_BACK);
//    glFrontFace(GL_CW);
}

void Renderer::Draw(){
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	glClearDepth(1.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glUseProgram(shaderProgram);

    glBindVertexArray(vao1);

	glUniform3f(offsetUnf, 1.5f, 0.5f, 0.0f);

//	glBindBuffer(GL_ARRAY_BUFFER, vertexDataVBO);
	glDrawArrays(GL_TRIANGLES, 0, 36);

	glUniform3f(offsetUnf, 0, 1, 0);
	glDrawArrays(GL_TRIANGLES, 0, 36);

	glUseProgram(0);

	glfwSwapBuffers();

//	std::cout<<"Error: "<<glGetError()<<std::endl;
}

unsigned int Renderer::Pick(int x, int y){
    unsigned char pixel[3];
//
//    Setup3DPick();
//    glTranslatef(0, 0, -10);
//    glRotatef(45, 1, 1, 0);
//
//    std::vector<Module*> modules = gamestate->GetModules();
//    for(Module* module:modules){
//        module->Pick();
//    }
//    glReadPixels(x, 768-y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &pixel);
    return (unsigned int)pixel[0]+(unsigned int)pixel[1]*256+(unsigned int)pixel[2]*256*256;
}
